package com.rightrule.sequence.dao.impl;

import com.rightrule.sequence.dao.SequenceDao;
import com.rightrule.sequence.lifecycle.AbstractLifeCycle;
import com.rightrule.sequence.range.SequenceRange;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.Assert;

import javax.sql.DataSource;
import java.sql.*;
import java.util.Date;
import java.util.Objects;

/**
 * @author: xry
 * @version: DefaultSequenceDao.java 2024/9/6 21:41
 */
@Slf4j
public class DefaultSequenceDao extends AbstractLifeCycle implements SequenceDao {

    private static final int MIN_STEP = 1;
    private static final int MAX_STEP = 100000;
    private static final int DEFAULT_STEP = 1000;
    private static final int DEFAULT_RETRY_TIMES = 150;
    private static final int QUERY_SYS_DATE_RETRY_TIMES = 3;

    private static final String DEFAULT_TABLE_NAME = "sequence";
    private static final String DEFAULT_NAME_COLUMN_NAME = "name";
    private static final String DEFAULT_VALUE_COLUMN_NAME = "value";
    private static final String DEFAULT_GMT_MODIFIED_COLUMN_NAME = "gmt_modified";

    //private static final String DEFAULT_TNT_INST_ID_COLUMN_NAME = "tnt_inst_id";

    private static final long DELTA = 100000000L;
    // 精确到s，用getDate获取会丢失时间精度，直接精确到日期
    private static final String QUERY_SYS_DATE_SQL = "SELECT now() FROM dual";
    // 精确到毫秒
    private static final String QUERY_SYS_TIME_SQL = "SELECT now(6) FROM dual";
    // 配置信息，存成字符串
    protected String configStr = "";

    @Qualifier("sequenceDataSource")
    private DataSource dataSource;

    /**
     * 重试次数
     */
    private int retryTimes = DEFAULT_RETRY_TIMES;
    /**
     * 步长
     */
    private int step = DEFAULT_STEP;
    /**
     * 序列所在的表名
     */
    private String tableName = DEFAULT_TABLE_NAME;
    /**
     * 存储序列名称的列名
     */
    private String nameColumnName = DEFAULT_NAME_COLUMN_NAME;
    /**
     * 存储序列名称的列名(租户名称)
     */
    //private String tntInstIdColumnName = DEFAULT_TNT_INST_ID_COLUMN_NAME;
    /**
     * 存储序列值的列名
     */
    private String valueColumnName = DEFAULT_VALUE_COLUMN_NAME;
    /**
     * 存储序列最后更新时间的列名
     */
    private String gmtModifiedColumnName = DEFAULT_GMT_MODIFIED_COLUMN_NAME;

    private volatile String selectSql;
    private volatile String updateSql;

    @Override
    public SequenceRange nextRange(String name, String tntInstId) {
        Assert.notNull(name, "序列名称不能为空");

        long oldValue;
        long newValue;

        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        //dataSource = initDataSource();
        for (int i = 0; i < retryTimes; i++) {
            // 获取新的值
            try {
                // 获得连接
                conn = dataSource.getConnection();
                // 预编译sql
                stmt = conn.prepareStatement(getSelectSql());
                // 替换占位符参数
                stmt.setString(1, name);
                //stmt.setString(2, tntInstId);
                // 执行
                rs = stmt.executeQuery();
                // 结束查询
                rs.next();
                oldValue = rs.getLong(1);

                // 不能小于0
                Assert.isTrue(oldValue >= 0, "Sequence value cannot be less than zero, value = "
                        + oldValue + ", please check table " + getTableName());

                // 超过最大值限制了
                Assert.isTrue(oldValue <= Long.MAX_VALUE - DELTA, "Sequence value overflow, value = "
                        + oldValue + ", please check table " + getTableName());

                newValue = oldValue + getStep();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                closeJDBCResource(rs);
                rs = null;
                closeJDBCResource(stmt);
                stmt = null;
                closeJDBCResource(conn);
                conn = null;
            }

            // 将新的值回流到sequence表
            try {
                // 获得连接
                conn = dataSource.getConnection();
                // 预编译sql
                stmt = conn.prepareStatement(getUpdateSql());
                // 替换占位符参数
                stmt.setLong(1, newValue);
                stmt.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
                stmt.setString(3, name);
                stmt.setLong(4, oldValue);
                //stmt.setString(5, tntInstId);
                // 执行更新，获取更新行数
                int effectedRows = stmt.executeUpdate();
                if (effectedRows == 0) {
                    continue;
                }
                return new SequenceRange(oldValue + 1, newValue);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                closeJDBCResource(stmt);
                stmt = null;
                closeJDBCResource(conn);
                conn = null;
            }
        }
        throw new RuntimeException("RetryTimes over limit, retryTimes = " + retryTimes);
    }

    /**
     * select value from sequence where name = ? and tntInstId = ?
     *
     * @return
     */
    private String getSelectSql() {
        if (StringUtils.isBlank(selectSql)) {
            synchronized (this) {
                if (StringUtils.isBlank(selectSql)) {
                    StringBuffer sb = new StringBuffer();
                    sb.append("select ").append(getValueColumnName());
                    sb.append(" from ").append(getTableName());
                    sb.append(" where ").append(getNameColumnName()).append(" = ? ");
                    //sb.append(" where ").append(getNameColumnName()).append(" = ? and ");
                    //sb.append(getTntInstIdColumnName()).append(" = ? ");
                    selectSql = sb.toString();
                }
            }
        }
        return selectSql;
    }

    /**
     * update sequence set value = ?, gmt_modified = ?
     * where name = ? and value = ? and tntInstId = ?
     *
     * @return
     */
    private String getTableName() {
        return tableName;
    }

    private String getUpdateSql() {
        if (StringUtils.isBlank(updateSql)) {
            synchronized (this) {
                if (StringUtils.isBlank(updateSql)) {
                    StringBuffer sb = new StringBuffer();
                    sb.append("update ").append(getTableName());
                    sb.append(" set ").append(getValueColumnName()).append(" = ?, ");
                    sb.append(getGmtModifiedColumnName()).append(" = ? where ");
                    sb.append(getNameColumnName()).append(" = ? and ");
                    sb.append(getValueColumnName()).append(" = ? ");
                    //sb.append(getValueColumnName()).append(" = ? and ");
                    //sb.append(getTntInstIdColumnName()).append(" = ? ");
                    updateSql = sb.toString();
                }
            }
        }
        return updateSql;
    }

    /**
     * 关闭jdbc资源
     *
     * @param closeable
     */
    private void closeJDBCResource(AutoCloseable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (SQLException e) {
                log.warn("Could not close JDBC Resource", e);
            } catch (Exception e) {
                log.warn("Unexpected exception on closing JDBC Resource", e);
            }
        }
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public void setNameColumnName(String nameColumnName) {
        this.nameColumnName = nameColumnName;
    }

    //public void setTntInstIdColumnName(String tntInstIdColumnName) {
    //    this.tntInstIdColumnName = tntInstIdColumnName;
    //}
    //
    //private String getTntInstIdColumnName() {
    //    return tntInstIdColumnName;
    //}

    public void setValueColumnName(String valueColumnName) {
        this.valueColumnName = valueColumnName;
    }

    public void setGmtModifiedColumnName(String gmtModifiedColumnName) {
        this.gmtModifiedColumnName = gmtModifiedColumnName;
    }

    private String getNameColumnName() {
        return nameColumnName;
    }

    private String getValueColumnName() {
        return valueColumnName;
    }

    private String getGmtModifiedColumnName() {
        return gmtModifiedColumnName;
    }

    @Override
    public int getStep() {
        return step;
    }

    public void setStep(int step) {
        Assert.isTrue(step >= MIN_STEP && step <= MAX_STEP, "Property step out of range [" + MIN_STEP
                + " , " + MAX_STEP + "], step = " + step);
        this.step = step;
    }

    public void setRetryTimes(int retryTimes) {
        Assert.isTrue(retryTimes > 0, "Property retryTimes cannot be less than zero, retryTimes = "
                + retryTimes);
        this.retryTimes = retryTimes;
    }

    @Override
    public int getRetryTimes() {
        return retryTimes;
    }

    /**
     * SELECT now() FROM dual
     *
     * @return
     */
    @Override
    public Date getSystemDate() {
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        for (int i = 0; i < QUERY_SYS_DATE_RETRY_TIMES; i++) {
            // 获取新的值
            try {
                // 获得连接
                conn = dataSource.getConnection();
                // 预编译sql
                stmt = conn.prepareStatement(QUERY_SYS_DATE_SQL);
                // 执行
                rs = stmt.executeQuery();
                // 结束查询
                rs.next();
                Date date = rs.getDate(1);
                if (Objects.nonNull(date)) {
                    return date;
                }
            } catch (SQLException e) {
                log.error("getSystemDate error, current retry times: {}", (i + 1), e);
            } finally {
                closeJDBCResource(rs);
                rs = null;
                closeJDBCResource(stmt);
                stmt = null;
                closeJDBCResource(conn);
                conn = null;
            }
        }
        throw new RuntimeException("getSystemDate failed, RetryTimes over limit, retryTimes = " + retryTimes);
    }

    @Override
    public Timestamp getSystemTimestamp() {
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        for (int i = 0; i < QUERY_SYS_DATE_RETRY_TIMES; i++) {
            // 获取新的值
            try {
                // 获得连接
                conn = dataSource.getConnection();
                // 预编译sql
                stmt = conn.prepareStatement(QUERY_SYS_TIME_SQL);
                // 执行
                rs = stmt.executeQuery();
                // 结束查询
                rs.next();
                Timestamp ts = rs.getTimestamp(1);
                if (Objects.nonNull(ts)) {
                    return ts;
                }
            } catch (SQLException e) {
                log.error("getSystemTimestamp error, current retry times: {}", (i + 1), e);
            } finally {
                closeJDBCResource(rs);
                rs = null;
                closeJDBCResource(stmt);
                stmt = null;
                closeJDBCResource(conn);
                conn = null;
            }
        }
        throw new RuntimeException("getSystemTimestamp failed, RetryTimes over limit, retryTimes = " + retryTimes);
    }
}
